use ic_crypto_internal_basic_sig_cose::*;
use ic_crypto_internal_basic_sig_ecdsa_secp256r1::*;
use ic_crypto_internal_test_vectors::*;
use ic_crypto_sha2::Sha256;
use ic_types::crypto::{AlgorithmId, CryptoError, CryptoResult};

// A COSE-encoded ECDSA-P256 public key, with a signature over an example
// message.
const ECDSA_P256_PK_2_COSE_HEX: &str = "a501020326200121582051556cab67bc37cc806d4b0666b2553a35f8a96e1ea0025942a1f140b6e42d4e2258200b203014c786088b3525fd5a41ce16cec81de536186efdbc8f9ab9bf9df2f366";
const MSG_2_HEX: &str = "2f1b671a93f444b8ec77e0211f9624c9c2612182b864f0d4ac9d335f5b4fe50201000000537f91225ffff1e2912a0f8ca7a0ef61df01ae3d8898fca283036239259bab4f82";
const SIG_OF_MSG_2_WITH_ECDSA_P256_PK_1_DER_HEX: &str = "3045022100c69c75c6d6c449ea936094476e8bfcad90d831a6437a87117615add6d6a5168802201e2e4535976794286fa264eb81d7b14b3f168ab7f62ad5c0b9d6ebfc64eb0c8c";

#[test]
fn should_correctly_parse_cose_encoded_pk() {
    let pk_cose = hex::decode(ECDSA_P256_PK_2_COSE_HEX).unwrap();
    let _pk = parse_cose_public_key(&pk_cose).unwrap();
}

#[test]
fn should_correctly_parse_cose_encoded_rsa256_pk() {
    // Generated by Windows Hello
    let rs256_cose = hex::decode("a401030339010020590100b1a50f6db683a65ebe915a3277c80730da05bd5d0cef9df901a4f62fe7d2dadf7233b246dc06d0a86d862ee80d407a115bd0646500f0925baaedd923a7e128f3d423af95d6c3adbc210d5eb4f0f8b247bc263f78c3d145335cc4a427f6b093a43ad7e38e00c11420473a05c9f322f8ece56b72fb9109df2e74a7d49db83fedd7d91ec946f571813c30f2b29e008ccdc0ec5abf1b1989671a64a4dcacedf37736f8c92ca2092638231a71630c2e9d1971b8679219c31189578677c5c9191842ad1e410525938102552d3d2db7c16724e8028c2b6eb3a6af584389cec538d6d9f372517082badf14c0c4b8fd8f603d93a3a27074ee797f1e3010134a3b64505b7d2143010001").unwrap();

    let pk = parse_cose_public_key(&rs256_cose).unwrap();
    assert_eq!(pk.0, AlgorithmId::RsaSha256);
}

#[test]
fn should_reject_cose_encoded_rsa256_pk_with_unknown_alg() {
    // Here alg = 257 not the expected -257

    let bad_rs256_cose = hex::decode("A401030319010120590100A7078A1A8FDE64C537AE5CA8D4B3A9139D68050CF76E45E77DBE47CECEB162F7095ADB6260998775203AA42A444F865DEB995C2B70B548ECEE01695DEB069ED18744C12FD24AEACDA4B2B7A5E97E7167CAF7D4B8904CE20CA9A8928978CA957FF2D9FCAE0859618B0AD74C164FAF5AB1DE7D7228A89BD3F8B497CEF9E45E1203CC40EE252140157C331A584F3916E569A8C39573D542A3577FB12332EBD3C9F421C9EF8A23D5ACF6BA439F7C3D6B73BA4E56B9B8EFBC42A2E5E734B99FDF7AB046813E43C65C926793919A7AE54F71AAF57C6876001A0558BC847D7555B1AE71F56A70272D786BE69A23A21A56C426371BD9882D40E7ECA6B7DA5D8169B7030F2143010001").unwrap();

    let result = parse_cose_public_key(&bad_rs256_cose);

    assert_eq!(
        result,
        Err(CryptoError::AlgorithmNotSupported {
            algorithm: AlgorithmId::Placeholder,
            reason: "Algorithm not supported in COSE parser".to_string()
        })
    );
}

#[test]
fn should_reject_cose_encoded_rsa256_pk_with_missing_e() {
    // Valid except that `e` is missing

    let bad_rs256_cose = hex::decode("A301030339010020590100A7078A1A8FDE64C537AE5CA8D4B3A9139D68050CF76E45E77DBE47CECEB162F7095ADB6260998775203AA42A444F865DEB995C2B70B548ECEE01695DEB069ED18744C12FD24AEACDA4B2B7A5E97E7167CAF7D4B8904CE20CA9A8928978CA957FF2D9FCAE0859618B0AD74C164FAF5AB1DE7D7228A89BD3F8B497CEF9E45E1203CC40EE252140157C331A584F3916E569A8C39573D542A3577FB12332EBD3C9F421C9EF8A23D5ACF6BA439F7C3D6B73BA4E56B9B8EFBC42A2E5E734B99FDF7AB046813E43C65C926793919A7AE54F71AAF57C6876001A0558BC847D7555B1AE71F56A70272D786BE69A23A21A56C426371BD9882D40E7ECA6B7DA5D8169B7030F").unwrap();

    let result = parse_cose_public_key(&bad_rs256_cose);

    assert_eq!(
        result,
        Err(CryptoError::MalformedPublicKey {
            algorithm: AlgorithmId::RsaSha256,
            key_bytes: Some(bad_rs256_cose),
            internal_error: "Failed to parse COSE public key".to_string()
        })
    );
}

#[test]
fn should_correctly_parse_webauthn_cose_encoded_pk() {
    let pk_cose = hex::decode(test_data::WEBAUTHN_ECDSA_P256_PK_COSE_HEX).unwrap();
    let _pk = parse_cose_public_key(&pk_cose).unwrap();
}

#[test]
fn should_fail_parsing_a_corrupted_cose_encoded_pk() {
    let mut pk_cose = hex::decode(ECDSA_P256_PK_2_COSE_HEX).unwrap();
    pk_cose[0] += 1;
    let pk_result = parse_cose_public_key(&pk_cose);
    assert!(pk_result.is_err());
    assert!(pk_result.unwrap_err().is_malformed_public_key());
}

#[test]
fn should_correctly_verify_der_signature() {
    let result = get_der_cose_verification_result(
        SIG_OF_MSG_2_WITH_ECDSA_P256_PK_1_DER_HEX,
        ECDSA_P256_PK_2_COSE_HEX,
        MSG_2_HEX,
    );
    assert!(result.is_ok());
}

#[test]
fn should_fail_to_verify_on_wrong_message() {
    let mut wrong_msg_hex = String::from(MSG_2_HEX);
    wrong_msg_hex.push_str("ab");
    let result = get_der_cose_verification_result(
        SIG_OF_MSG_2_WITH_ECDSA_P256_PK_1_DER_HEX,
        ECDSA_P256_PK_2_COSE_HEX,
        &wrong_msg_hex,
    );
    assert!(result.is_err());
    assert!(result.unwrap_err().is_signature_verification_error());
}

#[test]
fn should_fail_to_verify_corrupted_signature() {
    let mut corrupted_der_sig_hex = String::from(SIG_OF_MSG_2_WITH_ECDSA_P256_PK_1_DER_HEX);
    corrupted_der_sig_hex.truncate(corrupted_der_sig_hex.len() - 2);
    corrupted_der_sig_hex.push_str("aa");
    assert!(
        corrupted_der_sig_hex != SIG_OF_MSG_2_WITH_ECDSA_P256_PK_1_DER_HEX,
        "Signature should be different"
    );
    let result = get_der_cose_verification_result(
        &corrupted_der_sig_hex,
        ECDSA_P256_PK_2_COSE_HEX,
        MSG_2_HEX,
    );
    assert!(result.is_err());
    assert!(result.unwrap_err().is_signature_verification_error());
}

#[test]
fn should_correctly_verify_webauthn_signatures() {
    let result = get_der_cose_verification_result(
        test_data::ECDSA_P256_SIG_1_DER_HEX,
        test_data::ECDSA_P256_PK_1_COSE_HEX,
        test_data::WEBAUTHN_MSG_1_HEX,
    );
    assert!(result.is_ok());

    let result = get_der_cose_verification_result(
        test_data::ECDSA_P256_SIG_2_DER_HEX,
        test_data::ECDSA_P256_PK_2_COSE_HEX,
        test_data::WEBAUTHN_MSG_2_HEX,
    );
    assert!(result.is_ok());
}

// Given a DER-encoded signature, a COSE-encoded ECDSA-P256 public key,
// and a message, computes and returns a signature verification result.
fn get_der_cose_verification_result(
    sig_der_hex: &str,
    pk_cose_hex: &str,
    msg_hex: &str,
) -> CryptoResult<()> {
    let sig_der = hex::decode(sig_der_hex).unwrap();
    let sig = signature_from_der(&sig_der).unwrap();
    let pk_cose = hex::decode(pk_cose_hex).unwrap();
    let (_alg_id, pk) = parse_cose_public_key(&pk_cose).unwrap();
    let pk = public_key_from_der(&pk).unwrap();
    let msg = hex::decode(msg_hex).unwrap();
    let msg_hash = Sha256::hash(&msg);
    verify(&sig, &msg_hash, &pk)
}
